﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace 夜雀食堂TAG搜索器
{
    /// <summary>
    /// 食物，表示夜雀食堂中的食物信息
    /// </summary>
    public class 食物
    {
        public string 首字母 { get; set; }
        public string 名称 { get; set; }
        public string 厨具 { get; set; }
        public float 耗时 { get; set; }
        public int 价格 { get; set; }
        public List<string> Tag { get; set; }
        public List<string> 负Tag { get; set; }
        public List<string> 材料 { get; set; }
    }

    /// <summary>
    /// 程序的基础设定
    /// </summary>
    public static class 程序设置
    {
        public static int AJAX_每次动态加载数 { get; set; } = 5;
        public static int 搜索间隔 { get; set; } = 300;
        public static int 料理空位总数 { get; set; } = 5;

    }

    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// 储存从Excel读取的原数据
        /// </summary>
        List<食物> 原数据 = new List<食物>();
        /// <summary>
        /// 储存搜索结果
        /// </summary>
        List<食物> 搜索结果 = new List<食物>();
        /// <summary>
        /// 储存显示结果
        /// </summary>
        ObservableCollection<食物> 显示列表 = new ObservableCollection<食物>();
        /// <summary>
        /// 临时储存Tag，之后若要增加功能会用到
        /// </summary>
        Dictionary<string, string> Tag列表 = new Dictionary<string, string>();
        /// <summary>
        /// 搜索队列，储存需要搜索的内容
        /// </summary>
        Queue<string> 搜索队列 = new Queue<string>();
        int 已显示项 = 0;
        int 共显示项 = 0;

        /// <summary>
        /// 线程访问锁，用于搜索数据加载
        /// </summary>
        static object 锁 = new object();
        public MainWindow()
        {
            InitializeComponent();
            设定窗口位置(65, 100);
            添加料理信息();
            设置数据绑定<食物>(查找列表, 显示列表, 锁);

        }
        /// <summary>
        ///  设定窗口在屏幕中的位置
        /// </summary>
        /// <param name="高度百分比">从上到下的高度百分比</param>
        /// <param name="宽度百分比">从左到右的宽度百分比</param>
        private void 设定窗口位置(double 高度百分比, double 宽度百分比)
        {
            //设定不在任务栏显示该窗口
            //this.ShowInTaskbar = false;
            //获取当前显示设定
            var 屏幕长度 = System.Windows.SystemParameters.PrimaryScreenWidth;
            var 屏幕高度 = System.Windows.SystemParameters.PrimaryScreenHeight;
            //设置窗口位置
            this.Top = 屏幕高度 * (高度百分比 * 0.01) - this.Height;
            this.Left = 屏幕长度 * (宽度百分比 * 0.01) - this.Width;
            this.Topmost = true;
        }

        /// <summary>
        /// 设置一个数据绑定，将某个Listbox控件绑定到数据源，并使用锁以保证多线程访问
        /// </summary>
        /// <typeparam name="数据模型"></typeparam>
        /// <param name="绑定控件"></param>
        /// <param name="数据源"></param>
        /// <param name="线程锁"></param>
        private void 设置数据绑定<数据模型>(ListBox 绑定控件, ObservableCollection<数据模型> 数据源, object 线程锁)
        {
            Binding 绑定 = new Binding
            {
                Source = 数据源,
                IsAsync = true
            };
            绑定控件.SetBinding(ListView.ItemsSourceProperty, 绑定);
            BindingOperations.EnableCollectionSynchronization(数据源, 线程锁);
        }

        /// <summary>
        /// 从Excel文件获取料理表
        /// </summary>
        /// <returns></returns>
        public DataTable 获取料理表()
        {
            //取第一个表格
            var 表格集 = System.IO.Directory.GetFiles(System.Environment.CurrentDirectory, "*.xlsx");
            if (表格集.Length == 0) { MessageBox.Show("没有加载到一张食物表，无法开始查询"); }
            MiniExcel辅助类 miniExcel = new MiniExcel辅助类(表格集.FirstOrDefault());
            var 料理表 = miniExcel.从Excel转DataTable("料理", true);
            return 料理表;
        }

        /// <summary>
        /// 从DateTable中读取料理信息
        /// </summary>
        public void 添加料理信息()
        {
            var 料理集 = 获取料理表();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 料理集.Rows.Count; i++)
            {
                if (string.IsNullOrEmpty(料理集.Rows[i]["厨具"].ToString())) { sb.Append($"表格中的第{i + 1}行的数据可能有问题，请检查数据是否完整\r\n"); continue; }
                食物 当前食物 = new 食物();
                当前食物.名称 = 料理集.Rows[i]["名称"].ToString();
                当前食物.首字母 = 拼音转换.取首字母(当前食物.名称);
                当前食物.厨具 = 料理集.Rows[i]["厨具"].ToString();
                var 耗时字段 = 料理集.Rows[i]["时间（50级）"].ToString();
                if (耗时字段 != "任意")
                {
                    当前食物.耗时 = float.Parse(耗时字段);
                }
                当前食物.价格 = int.Parse(料理集.Rows[i]["价格/円"].ToString());
                string 食材原文本 = 料理集.Rows[i]["食材"].ToString();
                当前食物.材料 = new List<string>();
                var 食材集 = 食材原文本.Split('、');
                foreach (var 食材 in 食材集)
                {
                    当前食物.材料.Add(食材);
                }
                string Tag原文本 = 料理集.Rows[i]["正特性"].ToString();
                Tag添加(当前食物, Tag原文本);
                string 负Tag原文本 = 料理集.Rows[i]["反特性"].ToString();
                负Tag添加(当前食物, 负Tag原文本);
                原数据.Add(当前食物);
            }
            if (sb.Length != 0)
            {
                MessageBox.Show(sb.ToString() + "\r\n已读取其他的可用数据", "文件提示");

            }
            原数据.Sort((a, b) => b.价格.CompareTo(a.价格));//按价格降序
            搜索结果.AddRange(原数据);
            显示列表.Clear();
            动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);
        }

        private void Tag添加(食物 当前食物, string Tag原文本)
        {
            if (string.IsNullOrEmpty(Tag原文本)) { return; }
            当前食物.Tag = new List<string>();
            var Tag集 = Tag原文本.Split('、');
            foreach (var Tag in Tag集)
            {
                当前食物.Tag.Add(Tag);
                if (!Tag列表.ContainsKey(Tag))//如果Tag字典没有这个Tag就添加tag到字典里去
                {
                    Console.WriteLine($"正在匹配{Tag}");
                    Tag列表.Add(Tag, 拼音转换.取首字母(Tag));
                }
            }
        }

        private void 负Tag添加(食物 当前食物, string Tag原文本)
        {
            if (string.IsNullOrEmpty(Tag原文本)) { return; }
            当前食物.负Tag = new List<string>();
            var Tag集 = Tag原文本.Split('、');
            foreach (var Tag in Tag集)
            {
                当前食物.负Tag.Add(Tag);
            }
        }

        /// <summary>
        /// 在料理List中匹配相关的料理
        /// </summary>
        /// <param name="要搜索的文字"></param>
        public void 搜索料理(string 要搜索的文字)
        {
            lock (锁)
            {
                if (string.IsNullOrEmpty(要搜索的文字))
                {
                    搜索结果.Clear();
                    搜索结果.AddRange(原数据);
                    显示列表.Clear();
                    动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);
                    return;
                }
                if (要搜索的文字.Contains('+') || 要搜索的文字.Contains('-')) //批量筛选
                {
                    var 提取结果 = 提取搜索Tag(要搜索的文字);
                    搜索结果.Clear();
                    IEnumerable<食物> 临时结果 = null;
                    if (拼音转换.判断是否全为英文(要搜索的文字)) //如果全是英文的话就只匹配首字母
                    {
                        foreach (var 正向tag in 提取结果.正向Tag)
                        {
                            要搜索的文字 = 正向tag.ToUpper();
                            var Tag名称首字母匹配 = Tag列表.Where(搜索值 => 搜索值.Value.Contains(要搜索的文字)).ToList();
                            Tag名称首字母匹配.Sort((x, y) => x.Key.Length.CompareTo(y.Key.Length));
                            var 匹配结果 = Tag名称首字母匹配.FirstOrDefault();
                            if (临时结果 == null)
                            {
                                临时结果 = 原数据.Where(搜索值 => 搜索值.Tag.Contains(匹配结果.Key));
                            }
                            else
                            {
                                临时结果 = 临时结果.Where(搜索值 => 搜索值.Tag.Contains(匹配结果.Key));
                            }
                        }
                        foreach (var 负向Tag in 提取结果.负向Tag)
                        {
                            要搜索的文字 = 负向Tag.ToUpper();
                            var Tag名称首字母匹配 = Tag列表.Where(搜索值 => 搜索值.Value.Contains(要搜索的文字)).ToList();
                            Tag名称首字母匹配.Sort((x, y) => x.Key.Length.CompareTo(y.Key.Length));
                            var 匹配结果 = Tag名称首字母匹配.FirstOrDefault();
                            if (临时结果 == null)
                            {
                                临时结果 = 原数据.Where(搜索值 => 搜索值.负Tag != null && 搜索值.负Tag.Contains(匹配结果.Key));
                            }
                            else
                            {
                                临时结果 = 临时结果.Where(搜索值 => 搜索值.负Tag != null && 搜索值.负Tag.Contains(匹配结果.Key));
                            }
                        }
                        搜索结果.AddRange(临时结果);

                        显示列表.Clear();
                        动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);
                        return;
                    }

                    foreach (var 正向tag in 提取结果.正向Tag)
                    {
                        if (临时结果 == null)
                        {
                            临时结果 = 原数据.Where(搜索值 => 搜索值.Tag.Contains(正向tag));
                        }
                        else
                        {
                            临时结果 = 临时结果.Where(搜索值 => 搜索值.Tag.Contains(正向tag));
                        }
                    }
                    foreach (var 负向Tag in 提取结果.负向Tag)
                    {
                        if (临时结果 == null)
                        {
                            临时结果 = 原数据.Where(搜索值 => 搜索值.负Tag != null && 搜索值.负Tag.Contains(负向Tag));
                        }
                        else
                        {
                            临时结果 = 临时结果.Where(搜索值 => 搜索值.负Tag != null && 搜索值.负Tag.Contains(负向Tag));

                        }
                    }
                    搜索结果.AddRange(临时结果);
                    显示列表.Clear();
                    动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);
                    return;
                }
                if (拼音转换.判断是否全为英文(要搜索的文字)) //如果全是英文的话就只匹配首字母
                {
                    要搜索的文字 = 要搜索的文字.ToUpper();
                    var 名称首字母匹配 = 原数据.Where(搜索值 => 搜索值.首字母.Contains(要搜索的文字));
                    var Tag名称首字母匹配 = Tag列表.Where(搜索值 => 搜索值.Value.Contains(要搜索的文字)).ToList();
                    Tag名称首字母匹配.Sort((x, y) => x.Key.Length.CompareTo(y.Key.Length));
                    var 匹配结果 = Tag名称首字母匹配.FirstOrDefault();
                    var Tag首字母匹配 = 原数据.Where(搜索值 => 搜索值.Tag.Contains(匹配结果.Key));
                    var 负Tag首字母匹配 = 原数据.Where(搜索值 => 搜索值.负Tag != null && 搜索值.负Tag.Contains(匹配结果.Key));
                    搜索结果.Clear();
                    搜索结果.AddRange(名称首字母匹配);
                    搜索结果.AddRange(Tag首字母匹配);
                    搜索结果.AddRange(负Tag首字母匹配);
                    显示列表.Clear();
                    动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);
                    return;
                }
                var 名称匹配 = 原数据.Where(搜索值 => 搜索值.名称.Contains(要搜索的文字));
                var 材料匹配 = 原数据.Where(搜索值 => 搜索值.材料.Contains(要搜索的文字));
                var Tag匹配 = 原数据.Where(搜索值 => 搜索值.Tag.Contains(要搜索的文字));
                var 负Tag匹配 = 原数据.Where(搜索值 => 搜索值.负Tag != null && 搜索值.负Tag.Contains(要搜索的文字));
                搜索结果.Clear();
                搜索结果.AddRange(名称匹配);
                搜索结果.AddRange(材料匹配);
                搜索结果.AddRange(Tag匹配);
                搜索结果.AddRange(负Tag匹配);
                显示列表.Clear();
                动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);

            }
        }

        /// <summary>
        /// 将文本字符串分割为Tag列表，但是好像有点问题，暂时不用
        /// </summary>
        /// <param name="字符串">待分割的文本</param>
        /// <returns></returns>
        private static (List<string> 正向Tag, List<string> 负向Tag) 提取搜索Tag_待用(string 字符串)
        {
            // 创建列表
            var 正向Tag = new List<string>();
            var 负向Tag = new List<string>();

            // 创建字符串构建器
            var 字符串构建器 = new StringBuilder();

            // 将字符串转换为字符数组
            var 文本列 = 字符串.ToCharArray();

            // 定义符号变量
            char 加号 = '+';

            // 遍历字符数组
            foreach (char 单文本 in 文本列)
            {
                // 判断字符是否是加号或减号
                switch (单文本)
                {
                    //默认都包含加号
                    case '+':
                    case '-':
                        // 如果有减号，则匹配进列表
                        AddTag(字符串构建器, 加号, 正向Tag, 负向Tag);
                        // 清空字符串构建器
                        字符串构建器.Clear();
                        // 更新符号变量
                        加号 = 单文本;
                        break;
                    default:
                        // 如果不是，那么追加字符到字符串构建器
                        字符串构建器.Append(单文本);
                        break;
                }
            }

            // 添加最后一个标签到列表
            AddTag(字符串构建器, 加号, 正向Tag, 负向Tag);

            // 返回元组
            return (正向Tag, 负向Tag);

            // 定义一个辅助方法，用来添加标签到列表
            void AddTag(StringBuilder 传入字符串构建器, char 符号, List<string> 传入正向Tag, List<string> 传入负向Tag)
            {
                // 获取标签字符串
                string tag = 字符串构建器.ToString();
                // 判断标签是否为空
                if (!string.IsNullOrEmpty(tag))
                {
                    // 根据符号添加到列表
                    if (符号 == '+')
                    {
                        正向Tag.Add(tag);
                    }
                    else if (符号 == '-')
                    {
                        负向Tag.Add(tag);
                    }
                }
            }
        }
        /// <summary>
        /// 将文本字符串分割为Tag列表，已弃用的递归实现
        /// </summary>
        /// <param name="字符串">待分割的文本</param>
        /// <returns></returns>
        private static (List<string> 正向Tag, List<string> 负向Tag) 提取搜索Tag(string 字符串)
        {
            var 正向Tag = new List<string>();
            var 负向Tag = new List<string>();
            寻找正起始数(字符串, new Queue<bool>());

            int 寻找正起始数(string 字串, Queue<bool> 正负队列, int 位置 = 0, int 上次存储位置 = 0)
            {

                int 本次寻找位置 = 字串.IndexOfAny(new char[2] { '+', '-' }, 位置);
                //是否是第一次匹配
                if (正负队列.Count == 0 && 字串[0] != '-')
                {
                    //加入第一次匹配结果
                    if (字串[0] != '-') { 正负队列.Enqueue(true); }
                    寻找正起始数(字串, 正负队列);
                    return 0;
                }
                if (本次寻找位置 <= 位置 && 字串[0] != '-')
                {
                    //加入还未匹配的项
                    if (正负队列.Count > 1)
                    {
                        string Tag名1 = 字串.Substring(上次存储位置, 位置 - 上次存储位置 - 1);
                        if (正负队列.Dequeue()) { 正向Tag.Add(Tag名1); }
                        else { 负向Tag.Add(Tag名1); }
                    }
                    string Tag名 = 字符串.Substring(位置, 字符串.Length - 位置);
                    if (正负队列.Dequeue()) { 正向Tag.Add(Tag名); }
                    else { 负向Tag.Add(Tag名); }
                    return 0;
                }
                if (本次寻找位置 < 0)
                {

                    //加入还未匹配的项
                    if (正负队列.Count > 1)
                    {
                        string Tag名1 = 字串.Substring(上次存储位置, 位置 - 上次存储位置 - 1);
                        if (正负队列.Dequeue()) { 正向Tag.Add(Tag名1); }
                        else { 负向Tag.Add(Tag名1); }
                    }
                    string Tag名 = 字符串.Substring(位置, 字符串.Length - 位置);
                    if (!string.IsNullOrEmpty(Tag名))
                    {
                        if (正负队列.Dequeue()) { 正向Tag.Add(Tag名); }
                        else { 负向Tag.Add(Tag名); }
                    }

                    return 0;

                }
                if (!(位置 <= 0))
                {
                    Console.WriteLine($"截取了{上次存储位置}到{位置 - 1}的字符{字串.Substring(上次存储位置, 位置 - 上次存储位置 - 1)}，为{正负队列.Peek()}");
                    string Tag名 = 字串.Substring(上次存储位置, 位置 - 上次存储位置 - 1);
                    //清除空项
                    if (string.IsNullOrEmpty(Tag名))
                    {
                        if (字串[本次寻找位置] == '+') { 正负队列.Enqueue(true); }
                        if (字串[本次寻找位置] == '-') { 正负队列.Enqueue(false); }

                        寻找正起始数(字串, 正负队列, ++本次寻找位置, 位置);
                        return 0;
                    }
                    if (正负队列.Dequeue()) { 正向Tag.Add(Tag名); }
                    else { 负向Tag.Add(Tag名); }
                }

                if (字串[本次寻找位置] == '+') { 正负队列.Enqueue(true); }
                if (字串[本次寻找位置] == '-') { 正负队列.Enqueue(false); }

                寻找正起始数(字串, 正负队列, ++本次寻找位置, 位置);
                return 0;
            }
            return (正向Tag, 负向Tag);
        }

        private void 筛选空位(int 数量)
        {
            显示列表.Clear();
            搜索结果 = 搜索结果.Where(x => x.材料.Count == 程序设置.料理空位总数 - 数量).ToList();
            动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);
        }

        private void 排序(int 排序模式)
        {
            显示列表.Clear();
            if (排序模式 == 1) { 搜索结果.Sort((y, x) => x.价格.CompareTo(y.价格)); } //模式一 价格排序
            if (排序模式 == 2) { 搜索结果.Sort((x, y) => x.耗时.CompareTo(y.耗时)); } //模式二 耗时排序
            动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);
        }

        /// <summary>
        /// 动态加载显示列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="动态列表"></param>
        /// <param name="数据源"></param>
        /// <param name="加载数据量"></param>
        public void 动态加载显示列表_AJAX<T>(ObservableCollection<T> 动态列表, List<T> 数据源, int 加载数据量)
        {
            var 当前显示项 = 动态列表.Count;
            var 总显示项 = 数据源.Count;
            var 已加载项数 = 0;
            for (int i = 当前显示项; i < 总显示项; i++)
            {
                if (已加载项数 == 加载数据量)
                {
   
                    已显示项 = 动态列表.Count; 共显示项 = 数据源.Count;
                    return;
                }
                动态列表.Add(数据源[i]);
                已加载项数++;
            }
            已显示项 = 动态列表.Count; 共显示项 = 数据源.Count;

        }

        #region 事件绑定
        /// <summary>
        /// 如果搜索栏中在输入文字时执行的内容
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void 搜索栏_TextChanged_1(object sender, TextChangedEventArgs e)
        {
            var 当前操作文本框 = (TextBox)sender;
            var 待搜索内容 = 当前操作文本框.Text;
            Task 新线程 = new Task(() =>
            {
                搜索队列.Enqueue(待搜索内容);
                Thread.Sleep(程序设置.搜索间隔);
                lock (锁)
                {
                    if (搜索队列.Count <= 1)
                    {
                        搜索料理(待搜索内容);
                    }
                    搜索队列.Dequeue();
                }

            });
            新线程.Start();
        }

        /// <summary>
        /// 如果滚动条正在滚动时执行的内容
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void 查找列表_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
           var 滚动条 = (ScrollViewer)e.OriginalSource;
            if (滚动条.VerticalOffset == 0) { return; }
            if (滚动条.VerticalOffset >= 滚动条.ScrollableHeight * 0.7)
            {
                动态加载显示列表_AJAX(显示列表, 搜索结果, 程序设置.AJAX_每次动态加载数);
            }
        }

        private void 关闭_Click(object sender, RoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }
        private void 筛选空位按钮(object sender, RoutedEventArgs e)
        {
            var 文本 = ((Button)sender).Content.ToString();
            搜索料理(搜索栏.Text);
            if (文本 == "消") { return; }
            筛选空位(int.Parse(文本));
        }
        private void 快速排序按钮(object sender, RoutedEventArgs e)
        {
            var 参数 = int.Parse(((Button)sender).Tag.ToString());
            排序(参数);
        }
        private void 查看对话按钮(object sender, RoutedEventArgs e)
        {
            var 用户回执 = MessageBox.Show("该功能仅为测试功能，用于查看游戏内的历史对话记录。由于涉及读取游戏的日志文件，可能有杀毒软件误报情况发生。要继续查看吗？", "查看游戏对话 - 提示",MessageBoxButton.OKCancel);
            if (用户回执 == MessageBoxResult.OK)
            {
                var 记录 = new 对话记录();
                记录.Show();
                记录.Activate();
                记录.更新对话内容();
            }
        }
        private void 查找列表_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
        {
                动态加载显示列表_AJAX(显示列表, 搜索结果, 1);   
        }
    }
    #endregion
}
